﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Windows;
using Silvermoon.Shapes;
using System.Drawing;
using Silvermoon.UI;
using Silvermoon.Controls.Forms;
using Silvermoon.Controls.Base;
using Silvermoon.Windows.Styles;
using System.Collections;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Windows Phone 7 like context menu with 1, 3 or 5 items and a dolly-zoom effect when opening.
    /// </summary>
    public class ContextMenu : ContextMenuBase, IItemHost
    {
        const int maxItems = 5;
        #region classes

        private class ContentPanel : CompositeControlBase
        {

        }

        private class TouchLocker : Control
        {
            protected internal override void OnHold(TouchEventArgs e)
            {
            }

            protected internal override void OnTap(TapEventArgs e)
            {
            }
        }

        private class AnchorContainer : Control
        {
            private ControlCollection controls;

            /// <summary>
            /// If overloaded, returns a ControlCollection used to store child controls.
            /// </summary>
            /// <returns>ControlCollection, otherwise null</returns>
            protected internal override ControlCollection GetVisibleControls()
            {
                if (controls == null) controls = new ControlCollection(this);
                return controls;
            }

            protected internal override void OnRender(Silvermoon.Core.Renderer renderer)
            {
                base.OnRender(renderer);
                renderer.RenderControls(controls);
            }
        }

        #endregion
        #region fields

        private bool isAnchored = false;
        private int itemHeight = 78;
        private Margin padding = new Margin(16, 3, 16, 3);
        private TouchLocker touchLocker;
        private AnchorContainer anchorContainer;
        private ContentPanel content;
        private ScaleTransform windowTransform;
        private StretchTransform contentTransform;
        private int windowOpacity;
        private float contentHeightScale = 1f;
        private ItemContainerCollection items;
        private Point anchorLocation;

        #endregion
        #region accessors

        public static PropertyAccessor<ContextMenu, float> AnchorWindowDepthProperty = PropertyAccessor.Register<ContextMenu, float>("AnchorWindowDepth",
            c => c.AnchorWindowDepth,
            (c, v) => c.AnchorWindowDepth = v);

        public static PropertyAccessor<ContextMenu, float> ContentHeightScaleProperty = PropertyAccessor.Register<ContextMenu, float>("ContentHeightScale",
            c => c.ContentHeightScale,
            (c, v) => c.ContentHeightScale = v);

        public static PropertyAccessor<ContextMenu, float> ContentWidthScaleProperty = PropertyAccessor.Register<ContextMenu, float>("ContentWidthScale",
            c => c.ContentWidthScale,
            (c, v) => c.ContentWidthScale = v);

        public static PropertyAccessor<ContextMenu, int> WindowOpacityProperty = PropertyAccessor.Register<ContextMenu, int>("WidowOpacity",
            c => c.WindowOpacity,
            (c, v) => c.WindowOpacity = v);

        #endregion
        #region ctor

        public ContextMenu()
            : base()
        {
            Styles = CreateDefaultStyle();

            items = new ItemContainerCollection(null);
            items.ListChanged += new System.ComponentModel.ListChangedEventHandler(OnItemsListChanged);
            windowTransform = new ScaleTransform();
            touchLocker = new TouchLocker { Margin = Margin.Zero };
            touchLocker.TouchUp += (s, e) => Hide();

            contentTransform = new StretchTransform();
            content = new ContentPanel { Margin = new Margin(0, null, 0, null), Transformation = contentTransform };
            anchorContainer = new AnchorContainer { Margin = Margin.Zero };
            VisibleControls.Add(anchorContainer, touchLocker, content);
        }


        public ContextMenu(params MenuItem[] items)
            : this()
        {
            foreach (MenuItem item in items) Items.Add(item);
        }

        protected override void Dispose(bool dispose)
        {
            DisposeObject(items);
            base.Dispose(dispose);
            UndoAnchor(false);
        }

        #endregion
        #region props

        public int SelectedItemIndex
        {
            get { return -1; }
            set { }
        }

        public Color MenuTextColor
        {
            get { return content.TextColor; }
            set { content.TextColor = value; }
        }

        /// <summary>
        /// Gets or sets the items for the context menu.
        /// </summary>
        public IEnumerable<Item> MenuItems
        {
            get { return items; }
            set { SetItems(value); }
        }

        /// <summary>
        /// Gets the collection of items in the context menu. 
        /// </summary>
        public IList<Item> Items
        {
            get { return items; }
        }

        protected float ContentWidthScale
        {
            get { return contentTransform.X; }
            set
            {
                if (contentTransform.X != value)
                {
                    contentTransform.X = value;
                    OnContentScaleChanged();
                }
            }
        }

        protected float ContentHeightScale
        {
            get { return contentHeightScale; }
            set
            {
                if (contentHeightScale != value)
                {
                    contentHeightScale = value;
                    OnContentScaleChanged();
                }
            }
        }

        protected int WindowOpacity
        {
            get { return windowOpacity; }
            set
            {
                if (windowOpacity != value)
                {
                    windowOpacity = value;
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        /// <summary>
        /// Gets or sets how deep the window of the anchor control is translated.
        /// </summary>
        protected float AnchorWindowDepth
        {
            get { return windowTransform.X; }
            set
            {
                if (windowTransform.X != value)
                {
                    windowTransform.X = value;
                    windowTransform.Y = value;
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        /// <summary>
        /// Gets or sets the shape used to render the background of the menu.
        /// </summary>
        public Shape MenuBackground
        {
            get { return content.Background; }
            set
            {
                if (content.Background != value)
                {
                    content.Background = value;
                    OnMenuBackgroundChanged();
                }
            }
        }

        /// <summary>
        /// Gets or sets the color for the menu content.
        /// </summary>
        public override Color Color
        {
            get { return content.Color; }
            set
            {
                content.Color = value;
                OnColorChanged();
            }
        }

        #endregion
        #region methods


        void OnItemsListChanged(object sender, System.ComponentModel.ListChangedEventArgs e)
        {
            CheckMaxItems();
            Invalidate(InvalidationFlag.Layout);
        }

        private void CheckMaxItems()
        {
            if (items.Count > maxItems) throw new ArgumentOutOfRangeException("Items.Count exceeds limit of " + maxItems.ToString() + " items.");
        }

        protected void OnContentScaleChanged()
        {
            float s = 4f / (float)content.Height;
            contentTransform.Y = (1f - s) * contentHeightScale + s;
            content.Invalidate(InvalidationFlag.Transformation);
        }

        protected override void OnLayout()
        {
            if (Background != null) Background.Opacity = windowOpacity;
            if (CheckFlag(InvalidationFlag.Layout | InvalidationFlag.Size))
            {
                LayoutItems();
            }
            base.OnLayout();
        }

        private void LayoutItems()
        {
            var controls = content.VisibleControls;
            controls.Clear();
            Rectangle bounds = padding.LayoutChild(ClientBounds);
            int top = bounds.Top;
            int left = bounds.Left;
            int w = bounds.Width;
            int index = 0;
            foreach (Item item in items)
            {
                item.Attach(index++, null, this);
                Margin m = item.Margin;
                Size size = new Size(w, itemHeight);
                item.Bounds = m.Layout(item.Bounds, size);
                item.X += left;
                item.Y += top;
                top += itemHeight;
                controls.Add(item);
            }
        }

        protected int GetContentHeight()
        {
            int h = padding.Height + items.Count * itemHeight;
            return h;
        }

        protected virtual void OnMenuBackgroundChanged()
        {
        }

        protected override Shape CreateDefaultBackground()
        {
            Shape shape = PhoneColors.Black;
            shape.Opacity = 100;
            return shape;
        }

        protected virtual Shape CreateMenuBackground()
        {
            //return PhoneColors.Light;
            return new ModelContextMenuShape();
        }

        protected override void ApplyStyle()
        {
            base.ApplyStyle();
            if (MenuBackground == null) MenuBackground = CreateMenuBackground();
            if (Color.IsEmpty) Color = PhoneColors.White;
            if (content.IsTextColorEmpty) content.TextColor = PhoneColors.Dark;

        }

        protected override void OnShowing()
        {
            // recalculate anchorBounds at CalculatePosition():
            anchorBounds = Rectangle.Empty;

            Margin = Margin.Zero;
            Control anchor = AnchorControl;
            if (anchor != null)
            {
                anchorLocation = anchor.PointToScreen(Point.Empty);
                anchor.Visible = false;
                isAnchored = true;
                anchor.Window.Transformation += windowTransform;
            }
            base.OnShowing();
        }

        protected override void OnHidden()
        {
            UndoAnchor(true);
            base.OnHidden();
            AnchorControl = null;
        }

        protected override void OnVisibilityChanged(float oldValue)
        {
            float visibility = Visibility;
            Visible = visibility != 0f;

            if (visibility == 0f) OnHidden();
            else if (visibility == 1f) OnShown();
        }


        public void UndoAnchor(bool completed)
        {
            Control anchor = AnchorControl;

            isAnchored = false;
            if (anchor != null)
            {
                anchor.Visible = true;
                if (completed)
                {
                    Window w = anchor.Window;
                    if (w != null) w.Transformation -= windowTransform;
                }
            }
        }

        private Rectangle anchorBounds;

        /// <summary>
        /// Calculates the location of the menu based on AnchorControl and AnchorPoint.
        /// </summary>
        public override void CalculatePosition()
        {
            if (anchorBounds.IsEmpty) anchorBounds = AnchorControl != null ? AnchorControl.Bounds : Rectangle.Empty;
            if (anchorBounds.IsEmpty) return;
            anchorBounds.Location = anchorLocation;

            int h = GetContentHeight();
            content.Height = h;
            int top = anchorBounds.Top - h;
            bool atBottom = true;
            if (top < 0)
            {
                top = anchorBounds.Bottom;
                atBottom = false;
            }
            if ((top + h) > Screen.Height)
            {
                top = Screen.Height - h;
            }
            contentTransform.YOffset = atBottom ? (float)h : 0f;
            contentTransform.XOffset = (float)anchorBounds.Left;
            this.content.Location = new Point(0, top);
        }

        protected override VisualStateManager GetDefaultVisibilityManager()
        {
            return FormVisibilityManager.ContextMenuTransition;
        }

        /// <summary>
        /// Sets the items to display.
        /// </summary>
        /// <param name="items">Enumeration of <see cref="ListViewItem"/>s.</param>
        public void SetItems(IEnumerable<Item> items)
        {
            this.items.RaiseListChangedEvents = false;
            this.items.Clear();
            if (items != null)
            {
                int index = 0;
                foreach (var item in items)
                {
                    item.ItemIndex = index++;
                    this.items.Add(item);
                }
            }
            this.items.RaiseListChangedEvents = true;
            CheckMaxItems();
            Invalidate(InvalidationFlag.Layout);
        }

        public virtual void OnTapItem(Item item)
        {
            if (TapItem != null)
            {
                ItemEventArgs e = new ItemEventArgs(item.ItemIndex, item, item.DataValue, item.Host);
                TapItem(this, e);
            }
            Hide();
        }

        private void OnItemTap(Item item, int index)
        {
            index = items.IndexOf(item);
            if (index >= 0) OnTapItem(item);
        }

        private StyleDictionary CreateDefaultStyle()
        {
            //var result = new StyleDictionary();
            //result.AddClass<ListViewItem>(new Style { Background = new RoundedEdgeShape(), Color = PhoneColors.Transparent, StateManager = MenuItem.MenuItemManager });
            //return result;
            return null;
        }


        protected internal override void OnCommand(CommandEventArgs args)
        {
            base.OnCommand(args);
            if (!args.Handled)
            {
                UndoAnchor(false);
                AnchorControl.OnCommand(args);
            }
        }

        public MenuItem AddItem(string text)
        {
            var item = new MenuItem(text);
            Items.Add(item);
            return item;
        }

        public MenuItem Add(string text, ICommand command)
        {
            var item = new MenuItem(text, command);
            Items.Add(item);
            return item;
        }


        protected override void OnToolTipCommand(CommandEventArgs args)
        {
            // do nothing here
        }

        #endregion
        #region events

        public event EventHandler<ItemEventArgs> TapItem;


        #endregion
        #region IItemHost Members

        bool IItemHost.IsDynamicItemHeightSupported
        {
            get { return false; }
        }

        public void SetItemHeight(int index, int height)
        {
            // not supported.
        }


        public void BringItemIntoView(int itemIndex, bool animated)
        {
        }

        public void BringItemRectIntoView(int itemIndex, Rectangle rect, bool animated)
        {
        }

        bool IItemHost.IsItemSelected(Item item)
        {
            return item.IsSelected;
        }

        Item IItemHost.GetRealizedItem(int index)
        {
            return items[index];
        }

        IList IItemHost.ItemsSource
        {
            get { return items; }
        }

        protected internal override void OnRender(Silvermoon.Core.Renderer renderer)
        {
            base.OnRender(renderer);
            RenderAnchor(renderer);
        }

        private void RenderAnchor(Silvermoon.Core.Renderer renderer)
        {
            if (isAnchored)
            {
                Control anchor = AnchorControl;
                if (anchor != null && anchor.Parent != null)
                {
                    Point loc = anchor.Location;
                    anchor.Visible = true;
                    anchor.Location = anchorLocation;
                    try
                    {
                        renderer.RenderControl(anchor);
                    }
                    finally
                    {
                        anchor.Location = loc;
                        anchor.Visible = false;
                    }
                }
            }
        }

        #endregion


    }
}
